import { IRecoverable } from "../../base/pool/IRecoverable";
import { Pool } from "../../base/pool/Pool";
import { app } from "../../base/typings/global";
import { FsmBase } from "./FsmBase";
import { IFsm } from "./IFsm";
import { IFsmState } from "./IFsmState";

/**
 * 有限状态机
 * @type 有限状态机持有者类型
 */
export class Fsm<OwnerT, DataT> extends FsmBase implements IFsm<OwnerT, DataT>, IRecoverable{
    private static pool:Pool<Fsm<any, any>> = new Pool(Fsm);
    private _owner:OwnerT|null;
    private _states:Map<string, IFsmState<OwnerT, DataT>>;
    private _data:DataT;
    private _currentSate:IFsmState<OwnerT, DataT>|null;
    private _currentStateTime:number;
    private _isDestroyed:boolean;

    constructor(){
        super();
        this._states = new Map();
        this._currentStateTime = 0;
        this._isDestroyed = true;
    }

    get ownerType():string{
        return this.owner!.constructor.name;
    }

    protected _tick(dt: number): void {
        if(this._currentSate){
            this._currentStateTime += dt;
            this._currentSate.onUpdate && this._currentSate.onUpdate(this, dt);
        }
    }

    get owner(): OwnerT|null{
        return this._owner;
    }

    get fmsStateCount(): number{
        return this._states.size;
    }

    get isRunning(): boolean{
        return this._currentSate != null;
    }
    
    get isDestroyed(): boolean{
        return this._isDestroyed;
    }

    get currentState(): IFsmState<OwnerT, DataT>|null{
        return this._currentSate;
    }

    get currentStateTime(): number{
        return this._currentStateTime;
    }

    get currentStateName(): string{
        return this._currentSate ? this._currentSate.constructor.name : ""
    }

    /**
     * 创建有限状态机
     * @param name 有限状态机名称
     * @param owner 有限状态机持有者
     * @param states 有限状态机集合
     * @returns 创建的有限状态机
     */
    public static create<T, DataT>(name:string, owner:T, states:IFsmState<T, DataT>[], data?:DataT):Fsm<T, DataT>{
        if(!owner){
            throw new Error("FSM owner is invalid.");
        }
        if(!states || states.length == 0){
            throw new Error("FSM states is invalid.");
        }

        let fsm:Fsm<T, DataT> = Fsm.pool.allocate(()=>{
            return new Fsm<T, DataT>()
        });
        fsm._name = name;
        fsm._owner = owner;
        fsm._data = data;
        fsm._isDestroyed = false;
        for (let i = 0; i < states.length; i++) {
            const state = states[i];
            if(fsm._states.has(state.name)){
                throw new Error(`FSM ${fsm.name} state ${state.name} is already exist.`);
            }
            fsm._states.set(state.name, state);
            state.onInit && state.onInit(fsm);
        }
        return fsm;
    }

    start(stateName:string):void {
        if(this.isRunning){
            throw new Error("FSM is running, can not start again.");
        }
        let state = this.getState(stateName);
        if(!state){
            throw new Error(`FSM ${this.fullName} can not start state ${stateName} which is not exist`);
        }
        app.timer.frameLoop(0, this, this._tick);

        this._currentStateTime = 0;
        this._currentSate = state;
        this._currentSate.onEnter(this);
    }

    hasState(stateName:string): boolean {
        return this._states.has(stateName);
    }

    getState<T extends IFsmState<OwnerT, DataT>>(stateName:string):T|undefined {
        return this._states.get(stateName) as T || undefined;
    }

    getAllStates(): IFsmState<OwnerT, DataT>[] {
        let result:IFsmState<OwnerT, DataT>[] = [];
        this._states.forEach((value, key)=>{
            result.push(value);
        })
        return result;
    }

    changeState(stateName:string):void {
        if(!this._currentSate){
            throw new Error("current state is invalid. please start FSM first.")
        }

        let state = this.getState(stateName);
        if(!state){
            throw new Error(`"FSM '${this.name}' can not change state to ${stateName} which is not exist.`);
        }

        this._currentSate.onLeave && this._currentSate.onLeave(this, false);
        this._currentStateTime = 0;
        this._currentSate = state;
        this._currentSate.onEnter(this);
    }

    get data(): DataT {
        return this._data;
    }

    shutdown(): void {
        app.timer.clear(this, this._tick);
        Fsm.pool.recover(this);
    }

    reset(): void {
        if(this._currentSate){
            this._currentSate.onLeave && this._currentSate.onLeave(this, true);
        }

        this._states.forEach((value, key)=>{
            value.OnDestroy && value.OnDestroy(this);
        })

        this._states.clear();
        this._name = "";
        this._owner = null;
        this._currentSate = null;
        this._currentStateTime = 0;
        this._isDestroyed = true;
        this._data = null;
    }

    destroy(): void {
        this.reset();
    }

}